using System;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Threading;
using System.ComponentModel;
using System.Windows.Input;
using System.Diagnostics;
using System.Windows;

namespace PasswordProvider.View.Library
{

    internal class GlobalMouseHook
    {
        private class WinAPI
        {
            public const int WH_MOUSE_LL = 14;
            public const int WH_MOUSE = 7;

            public const int WM_LBUTTONDOWN = 0x201;
            public const int WM_RBUTTONDOWN = 0x204;
            public const int WM_MBUTTONDOWN = 0x207;

            [StructLayout(LayoutKind.Sequential)]
            public class POINT
            {
                public int x;
                public int y;
            }

            /// <summary>
            /// The MSLLHOOKSTRUCT structure contains information about a low-level keyboard input event. 
            /// </summary>
            [StructLayout(LayoutKind.Sequential)]
            public class MouseLLHookStruct
            {
                public POINT pt;
                public int mouseData;
                public int flags;
                public int time;
                public int dwExtraInfo;
            }

            [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
            public static extern int SetWindowsHookEx(
                int idHook,
                HookProc lpfn,
                IntPtr hMod,
                int dwThreadId);

            [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
            public static extern int UnhookWindowsHookEx(int idHook);

            [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
            public static extern int CallNextHookEx(
                int idHook,
                int nCode,
                int wParam,
                IntPtr lParam);

            public delegate int HookProc(int nCode, int wParam, IntPtr lParam);
        }

        #region Fields, Construction

        public delegate void GlobalMouseClickedEventHandler(Point point);
        public event GlobalMouseClickedEventHandler MouseClicked;

        private int _mouseHookHandle = 0;
        private WinAPI.HookProc _hookProc;

        private GlobalMouseHook()
        {
            SetupMouseHook();
        }

        ~GlobalMouseHook()
        {
            WinAPI.UnhookWindowsHookEx(_mouseHookHandle);
        }

        #endregion

        #region Singleton

        private static GlobalMouseHook _instance = new GlobalMouseHook();
        public static GlobalMouseHook Instance
        {
            get { return _instance; }
        }

        #endregion

        private void SetupMouseHook()
        {
            // Create an instance of HookProc.
            _hookProc = new WinAPI.HookProc(MouseHookProcedure);
            //install hook
            _mouseHookHandle = WinAPI.SetWindowsHookEx(
                    WinAPI.WH_MOUSE_LL,
                    _hookProc,
                    Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]),
                    0);
            //If SetWindowsHookEx fails.
            if (_mouseHookHandle == 0)
            {
                //Returns the error code returned by the last unmanaged function called using platform invoke that has the DllImportAttribute.SetLastError flag set. 
                int errorCode = Marshal.GetLastWin32Error();
                //Initializes and throws a new instance of the Win32Exception class with the specified error. 
                throw new Win32Exception(errorCode);
            }
        }

        private int MouseHookProcedure(int nCode, int wParam, IntPtr lParam)
        {
            // if ok and someone listens to our events
            if ((nCode >= 0))
            {
                //detect button clicked
                switch (wParam)
                {
                    case WinAPI.WM_LBUTTONDOWN:
                    case WinAPI.WM_RBUTTONDOWN:
                        //Marshall the data from callback.
                        WinAPI.MouseLLHookStruct mouseHookStruct = (WinAPI.MouseLLHookStruct)Marshal.PtrToStructure(lParam, typeof(WinAPI.MouseLLHookStruct));

                        MouseClicked(new Point(mouseHookStruct.pt.x, mouseHookStruct.pt.y));
                        break;

                }
            }
            //call next hook
            return WinAPI.CallNextHookEx(_mouseHookHandle, nCode, wParam, lParam);
        }

    }
}
